Failure diagnosis: diagnose(m) and friends ========================================== Models fail: a steady state that will not solve, a solution that does not exist or is not stable, a simulation that quietly turns into NaNs, a perfect-foresight path the solver cannot reach. RISE's failure-diagnosis suite turns each of those from a terse return code into a **diagnosis**: what check failed, observed versus required, who is implicated, and the next command to run. Two principles run through it: - **no silent failures** — anything that used to fail quietly now says where and why it died (governable, see the option below); - **the right vocabulary per model class** — eigenvalue (Blanchard-Kahn) counting is only ever used for constant-parameter models; for regime-switching models stability is judged by **mean-square stability (MSS)**, and the diagnosis speaks that language exclusively. One entry point --------------- :: diagnose(m) % prints the diagnosis for whatever fails first report = diagnose(m) % same, as a struct ``diagnose`` checks the steady state first, then the solution. What you get, by failure class: **Steady state fails.** The worst equations ranked against the engine's acceptance floor, labeled by their tag (or by the equation text itself for untagged models), the variables and parameters entering them, and a structural-rank check that names declared-but-never-used variables outright. When most equations are above the floor, the report says the failure is *widespread* — one global culprit (a bad discount/depreciation /growth number, a bad starting point) is more likely than the top-ranked equations — instead of overattributing. **No solution / unstable solution.** For a *constant-parameter* model: root counting, with the root table (the failed solve retains its eigenvalues), the stable-versus-predetermined comparison, and — for the indeterminate case — a clearly-labeled heuristic (in New-Keynesian models, check the Taylor principle). For a *regime-switching* model: the MSS spectral radius against the criterion, each regime's conditional spectral radius and persistence, and the culprit regime:: No mean-square-stable solution: ... The MSS spectral radius is 1.666, above the criterion 1 ... regime 1: conditional radius 0.9402, persistence 0.95 regime 2: conditional radius 1.322, persistence 0.95 Regime(s) 2 are conditionally explosive and, with the persistence above, the process spends long enough there for second moments to explode on average. Note what that example teaches: a regime *may* be conditionally explosive and the model still be MSS — the mix is what matters, and only the MSS radius decides. **Simulation diverges.** No more NaN-filled databases without a word: the failure site reports the first non-finite value (period, variable, regime), the growth profile of the run-up, and the pruning hint when simulating above order 1 unpruned. **Perfect foresight does not converge.** The stacked residual at the solver's final iterate is reshaped to (equation x period) and the worst cells are reported, plus the periods carrying 80% of the residual mass — "equation 3 around periods 12–15" localizes what "did not converge" cannot. Fix-actions ----------- Diagnosis ends with something to *do*, and two of the actions are automated: **Steady-state homotopy.** Every successful steady-state solve remembers its calibration. When a later calibration fails:: [report, mfixed] = diagnose(m, 'fix', "homotopy"); walks the parameters gradually from the last working calibration to the failing one, warm-starting each step from the previous solution. On success, ``mfixed`` comes back solved at the target. On a stall, **the breakpoint is the diagnosis**: "solvable up to 47% of the way — the target calibration likely admits no steady state", with ``mfixed`` solved at the breakpoint mix so you can inspect how the steady state degenerates. **Perfect-foresight shock continuation.** With ``simul_homotopy=true``, a failed stacked solve is retried by walking the shock size up from zero, each step warm-started from the previous path. A stall short of full size is reported as "solvable up to X% of the shock size" — the shock, not the solver, is the problem. The volume knob --------------- :: m = set(m, 'diagnostics_on_failure', 'warn'); % default % 'error' % throw instead % 'off' % legacy silence This gates every failure-site diagnostic (simulation divergence, PF worst cells, continuation summaries). Estimation is untouched by design — the likelihood loop stays silent at any setting. Related tools ------------- ``resid(m)`` (all steady-state residuals with labels), ``check_derivatives(m)`` (symbolic vs automatic vs numerical), ``rise.engine.solvers.perfect_foresight.multiplicity_diagnostic`` (is the path locally unique?), ``decipher(retcode)`` (any return code, in words — the registry messages themselves now state causes and point to ``diagnose``).